/*
 * GDevelop Core
 * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
 * reserved. This project is released under the MIT License.
 */

#ifndef ImageManager_H
#define ImageManager_H

#include <SFML/Graphics.hpp>
#include <SFML/OpenGL.hpp>
#include <SFML/System.hpp>
#include <iostream>
#include <memory>
#include <vector>
#include "GDCore/String.h"
namespace gd {
class ResourcesManager;
}
class OpenGLTextureWrapper;
class SFMLTextureWrapper;
#undef LoadImage  // thx windows.h

namespace gd {

/**
 * \brief Manage images for the IDE as well as at runtime for GD C++ Platform,
 * providing an easy way to get SFML images or OpenGL textures.
 *
 * Image manager is used by objects to obtain their images from the image name.
 *
 * Images are loaded dynamically when necessary, and are unloaded if there is no
 * more shared_ptr pointing on an image.
 *
 * You should in particular be interested by gd::ImageManager::GetOpenGLTexture
 * and gd::ImageManager::GetSFMLTexture.
 *
 * \see SFMLTextureWrapper
 * \see OpenGLTextureWrapper
 *
 * \ingroup ResourcesManagement
 */
class GD_CORE_API ImageManager {
 public:
  ImageManager();
  virtual ~ImageManager(){};

  /**
   * \brief Get a shared pointer to an OpenGL texture. The shared pointer must
   * be kept alive as long as the texture is used.
   */
  std::shared_ptr<OpenGLTextureWrapper> GetOpenGLTexture(
      const gd::String& name) const;

  /**
   * \brief Get a shared pointer to a SFML texture.  The shared pointer must be
   * kept alive as long as the texture is used.
   *
   * For example, if the texture is used in an object, you should store the
   * shared pointer in a member to make sure the texture is available as long as
   * the object is alive.
   */
  std::shared_ptr<SFMLTextureWrapper> GetSFMLTexture(
      const gd::String& name) const;

  /**
   * \brief Set the gd::ResourcesManager used by the ImageManager.
   */
  void SetResourcesManager(gd::ResourcesManager* resourcesManager_) {
    resourcesManager = resourcesManager_;
  }

  /**
   * \brief Load all images of the project which are flagged as alwaysLoaded.
   * \see ImageResource
   */
  void LoadPermanentImages();

  /**
   * \brief Check if a SFML texture with the specified name is available and
   * loaded in memory. \return true if the texture called \a name if available
   * and loaded in memory.
   */
  bool HasLoadedSFMLTexture(const gd::String& name) const;

  /**
   * \brief Add the SFMLTextureWrapper to loaded images ( so that it can be
   * accessed thanks to ImageManager::GetSFMLTexture ) with the specified name
   * and mark it as permanently loaded ( so that is is unloaded only when the
   * layout is unloaded ).
   */
  void SetSFMLTextureAsPermanentlyLoaded(
      const gd::String& name,
      std::shared_ptr<SFMLTextureWrapper>& texture) const;

  /**
   * \brief Reload a single image from the game resources
   */
  void ReloadImage(const gd::String& name) const;

#if defined(GD_IDE_ONLY)
  /**
   * \brief When called, images won't be unloaded from memory until
   * EnableImagesUnloading is called. Can be used when reloading a layout so as
   * to prevent images from being unloaded and then immediately reloaded.
   */
  void PreventImagesUnloading();

  /**
   * \brief Enable again unused images to be unloaded from memory.
   */
  void EnableImagesUnloading();
#endif

 private:
  mutable std::map<gd::String, std::weak_ptr<SFMLTextureWrapper> >
      alreadyLoadedImages;  ///< Reference all images loaded in memory.
  mutable std::map<gd::String, std::shared_ptr<SFMLTextureWrapper> >
      permanentlyLoadedImages;  ///< Contains (smart) pointers to images which
                                ///< should stay loaded even if they are not
                                ///< (currently) used.

#if defined(GD_IDE_ONLY)

  /** This list is filled, when PreventImagesUnloading is called, with images
   * already loaded in memory ( and any image loaded after the call to
   * PreventImagesUnloading ). It will thus prevent these images from being
   * unloaded. This list is destroyed when EnableImagesUnloading is called.
   *
   * \see PreventImagesUnloading
   * \see EnableImagesUnloading
   */
  mutable std::vector<std::shared_ptr<SFMLTextureWrapper> > unloadingPreventer;
  bool preventUnloading;  ///< True if no images must be currently unloaded.
#endif

  mutable std::map<gd::String, std::weak_ptr<OpenGLTextureWrapper> >
      alreadyLoadedOpenGLTextures;  ///< Reference all OpenGL textures loaded in
                                    ///< memory.

  mutable std::shared_ptr<SFMLTextureWrapper> badTexture;
  mutable std::shared_ptr<OpenGLTextureWrapper> badOpenGLTexture;

  gd::ResourcesManager* resourcesManager;
};

}  // namespace gd

/**
 * \brief Class wrapping an SFML texture.
 *
 * \see gd::ImageManager
 * \ingroup ResourcesManagement
 */
class GD_CORE_API SFMLTextureWrapper {
 public:
  SFMLTextureWrapper(const sf::Texture& texture);
  SFMLTextureWrapper();
  ~SFMLTextureWrapper();

  sf::Texture texture;
  sf::Image image;  ///< Associated sfml image, used for pixel perfect collision
                    ///< for example. If you update the image, call
                    ///< LoadFromImage on texture to update it also.
};

/**
 * \brief Class wrapping an OpenGL texture.
 *
 * \see gd::ImageManager
 * \ingroup ResourcesManagement
 */
class GD_CORE_API OpenGLTextureWrapper {
 public:
  OpenGLTextureWrapper(std::shared_ptr<SFMLTextureWrapper> sfmlTexture_);
  OpenGLTextureWrapper() : texture(0){};
  ~OpenGLTextureWrapper();
  inline GLuint GetOpenGLTexture() const { return texture; }

 private:
  std::shared_ptr<SFMLTextureWrapper> sfmlTexture;
  GLuint texture;
};

#endif  // ImageManager_H
